/**
 * Loads a Wavefront .mtl file specifying materials
 *
 * @author angelxuanchang
 */
export default function (THREE) {
	let {
		Color,
		DefaultLoadingManager,
		FileLoader,
		FrontSide,
		Loader,
		LoaderUtils,
		MeshPhongMaterial,
		RepeatWrapping,
		TextureLoader,
		Vector2
	} = THREE;

	var MTLLoader = function (manager) {

		Loader.call(this, manager);

	};

	MTLLoader.prototype = Object.assign(Object.create(Loader.prototype), {

		constructor: MTLLoader,

		/**
		 * Loads and parses a MTL asset from a URL.
		 *
		 * @param {String} url - URL to the MTL file.
		 * @param {Function} [onLoad] - Callback invoked with the loaded object.
		 * @param {Function} [onProgress] - Callback for download progress.
		 * @param {Function} [onError] - Callback for download errors.
		 *
		 * @see setPath setResourcePath
		 *
		 * @note In order for relative texture references to resolve correctly
		 * you must call setResourcePath() explicitly prior to load.
		 */
		load: function (url, onLoad, onProgress, onError) {

			var scope = this;

			var path = (this.path === '') ? LoaderUtils.extractUrlBase(url) : this.path;

			var loader = new FileLoader(this.manager);
			loader.setPath(this.path);
			loader.load(url, function (text) {

				onLoad(scope.parse(text, path));

			}, onProgress, onError);

		},

		setMaterialOptions: function (value) {

			this.materialOptions = value;
			return this;

		},

		/**
		 * Parses a MTL file.
		 *
		 * @param {String} text - Content of MTL file
		 * @return {MTLLoader.MaterialCreator}
		 *
		 * @see setPath setResourcePath
		 *
		 * @note In order for relative texture references to resolve correctly
		 * you must call setResourcePath() explicitly prior to parse.
		 */
		parse: function (text, path) {

			var lines = text.split('\n');
			var info = {};
			var delimiter_pattern = /\s+/;
			var materialsInfo = {};

			for (var i = 0; i < lines.length; i++) {

				var line = lines[i];
				line = line.trim();

				if (line.length === 0 || line.charAt(0) === '#') {

					// Blank line or comment ignore
					continue;

				}

				var pos = line.indexOf(' ');

				var key = (pos >= 0) ? line.substring(0, pos) : line;
				key = key.toLowerCase();

				var value = (pos >= 0) ? line.substring(pos + 1) : '';
				value = value.trim();

				if (key === 'newmtl') {

					// New material

					info = { name: value };
					materialsInfo[value] = info;

				} else {

					if (key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke') {

						var ss = value.split(delimiter_pattern, 3);
						info[key] = [parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])];

					} else {

						info[key] = value;

					}

				}

			}

			var materialCreator = new MTLLoader.MaterialCreator(this.resourcePath || path, this.materialOptions);
			materialCreator.setCrossOrigin(this.crossOrigin);
			materialCreator.setManager(this.manager);
			materialCreator.setMaterials(materialsInfo);
			return materialCreator;

		}

	});

	/**
	 * Create a new THREE-MTLLoader.MaterialCreator
	 * @param baseUrl - Url relative to which textures are loaded
	 * @param options - Set of options on how to construct the materials
	 *                  side: Which side to apply the material
	 *                        FrontSide (default), THREE.BackSide, THREE.DoubleSide
	 *                  wrap: What type of wrapping to apply for textures
	 *                        RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
	 *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
	 *                                Default: false, assumed to be already normalized
	 *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
	 *                                  Default: false
	 * @constructor
	 */

	MTLLoader.MaterialCreator = function (baseUrl, options) {

		this.baseUrl = baseUrl || '';
		this.options = options;
		this.materialsInfo = {};
		this.materials = {};
		this.materialsArray = [];
		this.nameLookup = {};

		this.side = (this.options && this.options.side) ? this.options.side : FrontSide;
		this.wrap = (this.options && this.options.wrap) ? this.options.wrap : RepeatWrapping;

	};

	MTLLoader.MaterialCreator.prototype = {

		constructor: MTLLoader.MaterialCreator,

		crossOrigin: 'anonymous',

		setCrossOrigin: function (value) {

			this.crossOrigin = value;
			return this;

		},

		setManager: function (value) {

			this.manager = value;

		},

		setMaterials: function (materialsInfo) {

			this.materialsInfo = this.convert(materialsInfo);
			this.materials = {};
			this.materialsArray = [];
			this.nameLookup = {};

		},

		convert: function (materialsInfo) {

			if (!this.options) return materialsInfo;

			var converted = {};

			for (var mn in materialsInfo) {

				// Convert materials info into normalized form based on options

				var mat = materialsInfo[mn];

				var covmat = {};

				converted[mn] = covmat;

				for (var prop in mat) {

					var save = true;
					var value = mat[prop];
					var lprop = prop.toLowerCase();

					switch (lprop) {

						case 'kd':
						case 'ka':
						case 'ks':

							// Diffuse color (color under white light) using RGB values

							if (this.options && this.options.normalizeRGB) {

								value = [value[0] / 255, value[1] / 255, value[2] / 255];

							}

							if (this.options && this.options.ignoreZeroRGBs) {

								if (value[0] === 0 && value[1] === 0 && value[2] === 0) {

									// ignore

									save = false;

								}

							}

							break;

						default:

							break;

					}

					if (save) {

						covmat[lprop] = value;

					}

				}

			}

			return converted;

		},

		preload: function () {

			for (var mn in this.materialsInfo) {

				this.create(mn);

			}

		},

		getIndex: function (materialName) {

			return this.nameLookup[materialName];

		},

		getAsArray: function () {

			var index = 0;

			for (var mn in this.materialsInfo) {

				this.materialsArray[index] = this.create(mn);
				this.nameLookup[mn] = index;
				index++;

			}

			return this.materialsArray;

		},

		create: function (materialName) {

			if (this.materials[materialName] === undefined) {

				this.createMaterial_(materialName);

			}

			return this.materials[materialName];

		},

		createMaterial_: function (materialName) {

			// Create material

			var scope = this;
			var mat = this.materialsInfo[materialName];
			var params = {

				name: materialName,
				side: this.side

			};

			function resolveURL(baseUrl, url) {

				if (typeof url !== 'string' || url === '')
					return '';

				// Absolute URL
				if (/^https?:\/\//i.test(url)) return url;

				return baseUrl + url;

			}

			function setMapForType(mapType, value) {

				if (params[mapType]) return; // Keep the first encountered texture

				var texParams = scope.getTextureParams(value, params);
				var map = scope.loadTexture(resolveURL(scope.baseUrl, texParams.url));

				map.repeat.copy(texParams.scale);
				map.offset.copy(texParams.offset);

				map.wrapS = scope.wrap;
				map.wrapT = scope.wrap;

				params[mapType] = map;

			}

			for (var prop in mat) {

				var value = mat[prop];
				var n;

				if (value === '') continue;

				switch (prop.toLowerCase()) {

					// Ns is material specular exponent

					case 'kd':

						// Diffuse color (color under white light) using RGB values

						params.color = new Color().fromArray(value);

						break;

					case 'ks':

						// Specular color (color when light is reflected from shiny surface) using RGB values
						params.specular = new Color().fromArray(value);

						break;

					case 'ke':

						// Emissive using RGB values
						params.emissive = new Color().fromArray(value);

						break;

					case 'map_kd':

						// Diffuse texture map

						setMapForType("map", value);

						break;

					case 'map_ks':

						// Specular map

						setMapForType("specularMap", value);

						break;

					case 'map_ke':

						// Emissive map

						setMapForType("emissiveMap", value);

						break;

					case 'norm':

						setMapForType("normalMap", value);

						break;

					case 'map_bump':
					case 'bump':

						// Bump texture map

						setMapForType("bumpMap", value);

						break;

					case 'map_d':

						// Alpha map

						setMapForType("alphaMap", value);
						params.transparent = true;

						break;

					case 'ns':

						// The specular exponent (defines the focus of the specular highlight)
						// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.

						params.shininess = parseFloat(value);

						break;

					case 'd':
						n = parseFloat(value);

						if (n < 1) {

							params.opacity = n;
							params.transparent = true;

						}

						break;

					case 'tr':
						n = parseFloat(value);

						if (this.options && this.options.invertTrProperty) n = 1 - n;

						if (n > 0) {

							params.opacity = 1 - n;
							params.transparent = true;

						}

						break;

					default:
						break;

				}

			}

			this.materials[materialName] = new MeshPhongMaterial(params);
			return this.materials[materialName];

		},

		getTextureParams: function (value, matParams) {

			var texParams = {

				scale: new Vector2(1, 1),
				offset: new Vector2(0, 0)

			};

			var items = value.split(/\s+/);
			var pos;

			pos = items.indexOf('-bm');

			if (pos >= 0) {

				matParams.bumpScale = parseFloat(items[pos + 1]);
				items.splice(pos, 2);

			}

			pos = items.indexOf('-s');

			if (pos >= 0) {

				texParams.scale.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
				items.splice(pos, 4); // we expect 3 parameters here!

			}

			pos = items.indexOf('-o');

			if (pos >= 0) {

				texParams.offset.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
				items.splice(pos, 4); // we expect 3 parameters here!

			}

			texParams.url = items.join(' ').trim();
			return texParams;

		},

		loadTexture: function (url, mapping, onLoad, onProgress, onError) {

			var texture;
			var manager = (this.manager !== undefined) ? this.manager : DefaultLoadingManager;
			var loader = manager.getHandler(url);

			if (loader === null) {

				loader = new TextureLoader(manager);

			}

			if (loader.setCrossOrigin) loader.setCrossOrigin(this.crossOrigin);
			texture = loader.load(url, onLoad, onProgress, onError);

			if (mapping !== undefined) texture.mapping = mapping;

			return texture;

		}

	};

	return { MTLLoader };
}